#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
评教运行器
使用DrissionPage库实现的评教流程运行器
"""

import os
import sys
import json
import logging
import time
import random
import traceback
from typing import Dict, Any, List, Optional
import argparse

# 添加项目根目录到路径，确保能正确导入模块
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

# 避免重复添加处理器的辅助函数
def get_logger(name):
    logger = logging.getLogger(name)
    # 如果已经添加了处理器，不再重复添加
    if not logger.handlers and not logging.getLogger().handlers:
        console_handler = logging.StreamHandler()
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        console_handler.setFormatter(formatter)
        logger.addHandler(console_handler)
    return logger

# 设置日志 - 使用辅助函数获取logger
logger = get_logger(__name__)

# 导入必要的模块
try:
    from .login_handler import LoginHandler
    from .json_handler import JsonHandler
except ImportError:
    # 绝对导入（当从其他目录调用时）
    from teaching_evaluation.drissionpage.login_handler import LoginHandler
    from teaching_evaluation.drissionpage.json_handler import JsonHandler

class EvaluationRunner:
    """
    评教运行器，自动完成整个评教流程
    """
    
    def __init__(self, config_path: str = None):
        """
        初始化评教运行器
        
        Args:
            config_path: 配置文件路径
        """
        # 设置日志 - 使用已存在的logger而不是创建新的
        self.logger = logger
        
        # 加载配置
        self.config = self._load_config(config_path)
        
        # 创建登录处理器
        login_config = self.config.get('login', {})
        self.login_handler = LoginHandler(login_config)
        
        # 加载评教概率配置
        self.probabilities = self.config.get('probabilities', {
            "teaching_quality": {
                "satisfied": 0.7,
                "neutral": 0.2,
                "unsatisfied": 0.1
            }
        })
        
        # 设置截图目录
        self.screenshot_dir = self.config.get('screenshot_dir', 'screenshots')
        os.makedirs(self.screenshot_dir, exist_ok=True)
        
        # 创建JSON处理器
        self.json_handler = JsonHandler(self.screenshot_dir)
    
    def _load_config(self, config_path: str) -> Dict[str, Any]:
        """
        加载配置文件
        
        Args:
            config_path: 配置文件路径
            
        Returns:
            Dict: 配置信息
        """
        default_config = {
            'login': {
                'login_url': 'http://wspj.gxmu.edu.cn/login.jsp',
                'username': '20234092832',
                'password': 'Gxmu@20234092832',
                'timeout': 30,
                'retry_count': 3
            },
            'probabilities': {
                "teaching_quality": {
                    "satisfied": 0.7,
                    "neutral": 0.2,
                    "unsatisfied": 0.1
                }
            },
            'screenshot_dir': 'screenshots'
        }
        
        if not config_path or not os.path.exists(config_path):
            self.logger.warning(f"配置文件不存在或未指定，使用默认配置")
            return default_config
        
        try:
            with open(config_path, 'r', encoding='utf-8') as f:
                config = json.load(f)
            self.logger.info(f"成功加载配置文件: {config_path}")
            return config
        except Exception as e:
            self.logger.error(f"加载配置文件失败: {e}，使用默认配置")
            return default_config
    
    def run(self) -> bool:
        """
        运行评教流程
        
        Returns:
            bool: 流程是否成功完成
        """
        try:
            # 配置文件已在初始化时加载，不需要再次加载
            # self._load_config()  # 移除这行
            
            # 创建登录处理器
            login_config = self.config.get('login', {})
            login_handler = LoginHandler(login_config)
            self.login_handler = login_handler  # 保存引用，供其他方法使用
            
            # 初始化浏览器
            if not login_handler.initialize():
                self.logger.error("浏览器初始化失败，退出程序")
                return False
            
            try:
                # 执行登录
                self.logger.info("开始登录...")
                login_success = login_handler.login()
                
                if login_success:
                    self.logger.info("登录成功！")
                    
                    # 尝试导航到评教页面
                    if login_handler.navigate_to_evaluation_page():
                        self.logger.info("已成功导航到评教页面")
                        
                        # 导入并调用评教点击器
                        try:
                            from .evaluation_clicker import run_clicker
                            self.logger.info("开始调用评教点击器...")
                            self.page = login_handler.page
                            
                            # 尝试点击评价按钮
                            if run_clicker(self.page):
                                self.logger.info("评教点击器成功点击评价按钮")
                            else:
                                self.logger.warning("评教点击器未找到可点击的评价按钮")
                                # 保存当前页面，帮助诊断问题
                                self._save_debug_info("no_evaluation_button_found")
                        except Exception as e:
                            self.logger.error(f"调用评教点击器时发生异常: {e}")
                            self.logger.error(traceback.format_exc())
                            
                        # 解析课程表格数据
                        self.page = login_handler.page
                        courses = self.parse_course_table()
                        
                        # 其他评教逻辑
                        
                        # 提取需要评教的课程列表
                        unfinished_courses = self._extract_course_list()
                        
                        if unfinished_courses:
                            for course in unfinished_courses:
                                if self._evaluate_course(course):
                                    self.logger.info(f"课程 {course.get('课程名称', '未知')} 评教成功")
                                else:
                                    self.logger.warning(f"课程 {course.get('课程名称', '未知')} 评教失败")
                                    
                                # 更新课程列表，确保JSON数据是最新的
                                self.parse_course_table()
                        else:
                            self.logger.warning("未找到需要评教的课程，退出程序")
                    else:
                        self.logger.error("导航到评教页面失败")
                        # 保存当前页面，帮助诊断问题
                        self._save_debug_info("navigation_failed")
                else:
                    self.logger.error("登录失败")
                    # 保存当前页面，帮助诊断问题
                    self._save_debug_info("login_failed")
                    
            except Exception as e:
                self.logger.error(f"执行过程中发生异常: {e}")
                self.logger.error(traceback.format_exc())
                self._save_debug_info("runtime_exception")
            finally:
                # 退出登录并关闭浏览器
                login_handler.logout()
            
            self.logger.info("评教流程执行完毕")
            return True
            
        except Exception as e:
            self.logger.error(f"执行评教流程时发生异常: {e}")
            self.logger.error(traceback.format_exc())
            return False
    
    def parse_course_table(self):
        """解析课程表格数据并保存到JSON文件"""
        try:
            self.logger.info("开始解析课程表格数据...")
            
            # 查找表格行元素
            table_rows = self.page.eles("css:table.data_table tr:not(:first-child)")
            if not table_rows:
                self.logger.warning("使用标准选择器未找到课程表格行元素，尝试使用HTML解析...")
                # 使用新的HTML解析方法
                courses = self.json_handler.parse_course_table_html(self.page)
            else:
                self.logger.info(f"找到 {len(table_rows)} 个课程行")
                # 使用标准解析方法
                courses = self.json_handler.parse_course_table_data(table_rows)
            
            # 使用JSON处理器保存课程数据
            if courses:
                if self.json_handler.save_courses(courses):
                    self.logger.info(f"成功保存 {len(courses)} 个课程数据")
            else:
                self.logger.warning("未找到任何课程数据")
            
            return courses
            
        except Exception as e:
            self.logger.error(f"解析课程表格数据失败: {e}")
            return []
    
    def _get_course_list(self) -> List[Dict[str, Any]]:
        """
        获取需要评教的课程列表
        
        Returns:
            List[Dict]: 课程列表
        """
        page = self.login_handler.page
        courses = []
        
        try:
            self.logger.info("尝试获取课程列表")
            # 这里原有的xpath查找课程元素的代码已删除
            
            return courses
            
        except Exception as e:
            self.logger.error(f"获取课程列表失败: {e}")
            return []
    
    def _enter_course_evaluation(self, course: Dict[str, Any]) -> bool:
        """
        进入课程评教页面
        
        Args:
            course: 课程信息
            
        Returns:
            bool: 是否成功进入评教页面
        """
        try:
            course_name = course.get('课程名称', '未知课程')
            teacher_name = course.get('教师姓名', '未知教师')
            
            self.logger.info(f"进入课程评教: {course_name} - {teacher_name}")
            
            # 尝试直接通过链接点击
            a_selector = f"css:a[onclick*=\"{course.get('课程代码', '')}\"]"
            if course.get('课程代码'):
                evaluate_link = self.page.ele(a_selector)
                if evaluate_link:
                    self.logger.info(f"找到课程评教链接，通过课程代码 {course.get('课程代码')} 定位")
                    evaluate_link.click()
                    time.sleep(0.8)  # 等待页面加载，改为800毫秒
                    
                    # 验证是否进入评教页面
                    current_url = self.page.url
                    if "evaluate" in current_url or "pj" in current_url:
                        self.logger.info(f"成功进入评教页面: {current_url}")
                        self._take_screenshot(f"evaluate_{course_name}")
                        return True
            
            # 检查是否有onclick属性信息
            data_attrs = course.get('data_attrs', {})
            if data_attrs:
                # 直接使用JavaScript调用评教函数
                js_code = f"""
                jshtck(
                    '{data_attrs.get("kolc", "")}',
                    '{data_attrs.get("实验课", "实验课") if data_attrs.get("实验课") else ""}',
                    '{data_attrs.get("kch", "")}',
                    '{data_attrs.get("kcmc", "")}',
                    '{data_attrs.get("xh", "")}',
                    '{data_attrs.get("pjjgid", "")}',
                    '{data_attrs.get("btn", "")}'
                );
                """
                self.logger.info("使用JavaScript执行评教跳转")
                result = self.page.run_js(js_code)
                time.sleep(0.8)  # 等待页面跳转，改为800毫秒
                
                # 验证是否进入评教页面
                current_url = self.page.url
                if "evaluate" in current_url or "pj" in current_url:
                    self.logger.info(f"成功进入评教页面: {current_url}")
                    self._take_screenshot(f"evaluate_{course_name}")
                    return True
                else:
                    self.logger.warning(f"JS跳转后URL不是评教页面: {current_url}")
            
            # 传统方式：查找评教按钮并点击
            # 定位包含课程名称和未评价标记的行
            badge_selector = "css:tr:has(.badge.badge-important)"
            badge_rows = self.page.eles(badge_selector)
            
            for row in badge_rows:
                row_text = row.text
                if course_name in row_text and teacher_name in row_text:
                    self.logger.info(f"找到匹配的未评价课程行: {course_name}")
                    
                    # 查找评教链接
                    a_element = row.ele("css:a")
                    if a_element:
                        self.logger.info("点击评教链接")
                        a_element.click()
                        time.sleep(0.8)  # 等待页面加载，改为800毫秒
                        
                        current_url = self.page.url
                        if "evaluate" in current_url or "pj" in current_url:
                            self.logger.info(f"成功进入评教页面: {current_url}")
                            self._take_screenshot(f"evaluate_{course_name}")
                            return True
            
            self.logger.warning(f"尝试所有方法后仍无法进入评教页面")
            return False
                
        except Exception as e:
            self.logger.error(f"进入课程评教失败: {e}")
            return False
    
    def _complete_evaluation(self) -> bool:
        """
        完成当前课程的评教
        
        Returns:
            bool: 评教是否成功完成
        """
        page = self.login_handler.page
        
        try:
            # 1. 找到所有单选题选项组
            radio_groups = self._find_radio_groups()
            if not radio_groups:
                self.logger.warning("未找到单选题选项组")
            
            # 2. 为每个选项组选择选项
            for i, group in enumerate(radio_groups):
                if not self._select_radio_option(group, i):
                    self.logger.warning(f"为第 {i+1} 组单选题选择选项失败")
            
            # 3. 查找文本输入框并填写内容
            text_areas = page.eles('css:textarea')
            for i, text_area in enumerate(text_areas):
                suggested_text = self._generate_evaluation_text(i)
                try:
                    text_area.input(suggested_text)
                    self.logger.info(f"成功填写第 {i+1} 个文本框")
                except Exception as e:
                    self.logger.error(f"填写第 {i+1} 个文本框失败: {e}")
            
            # 保存填写完成的评教表单
            self._take_screenshot("form_filled")
            
            # 4. 提交评教表单
            submit_button = page.ele('css:input[type="submit"], button[type="submit"], button:contains("提交")')
            if not submit_button:
                self.logger.error("未找到提交按钮")
                return False
            
            # 点击提交按钮
            submit_button.click()
            time.sleep(0.8)  # 等待提交完成，改为800毫秒
            
            # 5. 确认是否有确认对话框
            confirm_button = page.ele('css:button:contains("确定"), input[value*="确定"]')
            if confirm_button:
                confirm_button.click()
                time.sleep(0.8)  # 等待确认完成，改为800毫秒
            
            # 6. 验证是否提交成功
            success_message = page.ele('css:div:contains("成功"), div:contains("完成")')
            if success_message:
                self.logger.info("评教提交成功")
                return True
            
            # 检查是否返回到课程列表页
            if page.ele('css:.course-list, table.course-table'):
                self.logger.info("已返回课程列表，评教可能已成功")
                return True
            
            self.logger.warning("无法确认评教是否成功提交")
            return False
            
        except Exception as e:
            self.logger.error(f"完成评教过程中发生异常: {e}")
            return False
    
    def _find_radio_groups(self) -> List:
        """
        查找页面中的单选题选项组
        
        Returns:
            List: 单选题选项组元素列表
        """
        page = self.login_handler.page
        
        try:
            # 尝试不同的选择器查找单选题组
            # 1. 标准表单中的单选题组
            radio_groups = page.eles('css:tr:has(input[type="radio"])')
            
            # 2. 如果没找到，尝试查找所有包含radio的div组
            if not radio_groups:
                radio_groups = page.eles('css:div:has(input[type="radio"])')
            
            # 3. 如果仍然没找到，尝试直接按name属性分组
            if not radio_groups:
                all_radios = page.eles('css:input[type="radio"]')
                # 通过name属性分组
                radio_names = set()
                for radio in all_radios:
                    name = radio.attr('name')
                    if name:
                        radio_names.add(name)
                
                # 对每个name创建一个组
                radio_groups = []
                for name in radio_names:
                    group = page.eles(f'css:input[type="radio"][name="{name}"]')
                    if group:
                        radio_groups.append(group)
            
            self.logger.info(f"找到 {len(radio_groups)} 个单选题组")
            return radio_groups
            
        except Exception as e:
            self.logger.error(f"查找单选题组失败: {e}")
            return []
    
    def _select_radio_option(self, group, group_index: int) -> bool:
        """
        为单选题选项组选择选项
        
        Args:
            group: 单选题选项组元素
            group_index: 组索引
            
        Returns:
            bool: 选择是否成功
        """
        try:
            # 根据概率配置决定选择哪个选项
            probability_dist = self.probabilities.get("teaching_quality", {
                "satisfied": 0.7,
                "neutral": 0.2,
                "unsatisfied": 0.1
            })
            
            rand = random.random()
            
            # 获取选项元素
            if hasattr(group, 'eles'):  # 如果group是单个元素
                options = group.eles('css:input[type="radio"]')
            else:  # 如果group已经是元素列表
                options = group
            
            if not options:
                self.logger.error(f"第 {group_index+1} 组没有找到选项")
                return False
            
            # 确定要选哪个选项
            option_index = 0
            if rand < probability_dist["satisfied"]:
                # 选择满意类选项（通常是第一个选项）
                option_index = 0
            elif rand < probability_dist["satisfied"] + probability_dist["neutral"]:
                # 选择中立选项（通常是中间选项）
                option_index = len(options) // 2
            else:
                # 选择不满意选项（通常是最后一个选项）
                option_index = len(options) - 1
            
            # 确保索引有效
            option_index = min(option_index, len(options) - 1)
            
            # 点击选项
            options[option_index].click()
            self.logger.info(f"为第 {group_index+1} 组选择了第 {option_index+1} 个选项")
            return True
            
        except Exception as e:
            self.logger.error(f"为第 {group_index+1} 组选择选项失败: {e}")
            return False
    def _verify_all_completed(self) -> bool:
        """
        验证是否所有课程评教已完成
        
        Returns:
            bool: 是否全部完成
        """
        page = self.login_handler.page
        
        try:
            # 查找是否还有"未评"标记
            unfinished_elements = page.eles('css:td:contains("未评")')
            if not unfinished_elements:
                return True
            else:
                self.logger.warning(f"仍有 {len(unfinished_elements)} 门课程未完成评教")
                return False
                
        except Exception as e:
            self.logger.error(f"验证评教完成状态时发生异常: {e}")
            return False
    
    def _take_screenshot(self, name: str) -> Optional[str]:
        """
        保存当前页面截图
        
        Args:
            name: 截图名称
            
        Returns:
            Optional[str]: 截图路径，失败则返回None
        """
        timestamp = time.strftime("%Y%m%d_%H%M%S")
        filename = os.path.join(self.screenshot_dir, f"{name}_{timestamp}.png")
        return self.login_handler.take_screenshot(filename)

            
    def _extract_course_list(self) -> List[Dict[str, Any]]:
        """
        提取需要评教的课程列表
        
        Returns:
            List[Dict]: 课程列表
        """
        try:
            # 使用JSON处理器获取未评价的课程
            courses = self.json_handler.get_unevaluated_courses()
            
            if courses:
                self.logger.info(f"找到 {len(courses)} 门未评价的课程")
                for i, course in enumerate(courses):
                    self.logger.info(f"课程{i+1}: {course.get('课程名称')} - {course.get('教师姓名')}")
            else:
                self.logger.info("未找到需要评教的课程")
                
                # 如果没找到未评价课程，尝试直接从页面解析
                if self.page:
                    self.logger.info("尝试直接从页面解析未评价课程...")
                    
                    # 查找带有"未评价"标记的行
                    badge_rows = self.page.eles("css:tr:has(.badge.badge-important)")
                    if badge_rows:
                        self.logger.info(f"在页面上找到 {len(badge_rows)} 个未评价课程行")
                        courses = []
                        
                        for row in badge_rows:
                            course_data = self.json_handler.parse_course_from_html(row)
                            if course_data:
                                courses.extend(course_data)
                        
                        if courses:
                            self.logger.info(f"从页面成功解析 {len(courses)} 门未评价课程")
                            # 保存这些课程数据
                            self.json_handler.save_courses(courses)
            
            return courses
        except Exception as e:
            self.logger.error(f"提取课程列表失败: {e}")
            return []

    def _save_debug_info(self, error_type: str):
        """
        保存调试信息
        
        Args:
            error_type: 错误类型
        """
        try:
            if not hasattr(self, 'page') or not self.page:
                if hasattr(self, 'login_handler') and self.login_handler and self.login_handler.page:
                    self.page = self.login_handler.page
                else:
                    self.logger.error("无法保存调试信息：页面对象不存在")
                    return
            
            # 创建调试目录
            debug_dir = os.path.join("screenshots", "debug", error_type)
            os.makedirs(debug_dir, exist_ok=True)
            
            # 获取时间戳
            timestamp = time.strftime("%Y%m%d_%H%M%S")
            
            # 保存页面源代码
            html_path = os.path.join(debug_dir, f"page_{timestamp}.html")
            with open(html_path, "w", encoding="utf-8") as f:
                f.write(self.page.html)
            
            # 保存截图
            screenshot_path = os.path.join(debug_dir, f"screenshot_{timestamp}.png")
            self.page.get_screenshot(screenshot_path)
            
            # 保存URL和其他信息
            info_path = os.path.join(debug_dir, f"info_{timestamp}.json")
            debug_info = {
                "url": self.page.url,
                "title": self.page.title,
                "error_type": error_type,
                "timestamp": timestamp
            }
            with open(info_path, "w", encoding="utf-8") as f:
                json.dump(debug_info, f, ensure_ascii=False, indent=2)
            
            self.logger.info(f"已保存调试信息到 {debug_dir}")
            
        except Exception as e:
            self.logger.error(f"保存调试信息时发生异常: {e}")

def main():
    """主函数"""
    # 解析命令行参数
    parser = argparse.ArgumentParser(description='教学评价自动化工具')
    parser.add_argument('--config', help='配置文件路径')
    parser.add_argument('--log-level', default='INFO', help='日志级别')
    args = parser.parse_args()
    
    # 设置日志 - 检查是否已有处理器，避免重复配置
    log_level = getattr(logging, args.log_level.upper(), logging.INFO)
    logger = logging.getLogger()
    logger.setLevel(log_level)
    
    # 仅在没有处理器时添加新的处理器
    if not logger.handlers:
        # 创建控制台处理器
        console_handler = logging.StreamHandler()
        console_handler.setLevel(log_level)
        
        # 设置日志格式
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        console_handler.setFormatter(formatter)
        
        # 添加处理器到日志记录器
        logger.addHandler(console_handler)
    
    # 创建评教运行器
    runner = EvaluationRunner(args.config)
    
    # 运行评教流程
    success = runner.run()
    
    if success:
        logger.info("评教流程全部成功完成！")
    else:
        logger.warning("评教流程可能未完全成功，请检查日志和截图")

if __name__ == "__main__":
    main() 